package ru.osu.acm.system;

import ru.osu.acm.common.config.Config;
import ru.osu.acm.common.entities.Submission;
import ru.osu.acm.common.industrial.SubmissionFactory;
import ru.osu.acm.system.sourcediff.GST;
import ru.osu.acm.system.sourcediff.SourcePack;
import ru.osu.acm.system.sourcediff.SourceReformation;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * User: aleksey
 * Date: Sep 19, 2009
 * Time: 12:34:14 AM
 */
public class PlagTester {
    public static int PLAGSTATUS_QUEUED = 0;
    public static int PLAGSTATUS_CONFIRMED = 1;
    public static int PLAGSTATUS_SUSPECT = 2;
    public static int PLAGSTATUS_ILLEGAL = 3;
    public static int PLAGSTATUS_DUMMY = 4;
    public static int PLAGSTATUS_TESTING = 5;

    public static double PLAGCOEFF_CONFIRMED = 0.6;
    public static double PLAGCOEFF_SUSPECT = 0.85;
    public static double PLAGCOEFF_ILLEGAL = 1.0;

    private Connection con;

    private void connectToDatabase() throws SQLException {
        String database = Config.database;
        String username = Config.username;
        String password = Config.password;

        try {
            Class.forName(Config.driver);
        }
        catch (ClassNotFoundException e) {
            Logger.logError(e);
        }
        con = DriverManager.getConnection(Config.uri + database, username, password);
    }

    private void disconnectFromDatabase() throws SQLException {
        con.close();
    }

    private void updatePlagStatus(Submission submission, int plagStatus) throws SQLException {
        PreparedStatement statement = con.prepareStatement("update submission set plagStatus = ? where id = ?");
        statement.setInt(1, plagStatus);
        statement.setInt(2, submission.getId());
        statement.executeUpdate();
        statement.close();
    }

    private void updatePlagStatus(Submission submission, PlagTestResult result) throws SQLException {
        PreparedStatement statement = con.prepareStatement("update submission set plagStatus = ?, plagCoeff=?  where id = ?");
        statement.setInt(1, result.plagStatus);
        statement.setDouble(2, result.plagCoeff);
        statement.setInt(3, submission.getId());
        statement.executeUpdate();
        statement.close();
    }

    private void updatePlagStatusForRejected() throws SQLException {
        PreparedStatement statement = con.prepareStatement("update submission set plagStatus=" + PLAGSTATUS_DUMMY + " where status<>" + Tester.STATUS_ACCEPTED + " and status<>" + Tester.STATUS_RECEIVED);
        statement.executeUpdate();
        statement.close();
    }

    private class PlagTestResult {
        int plagStatus;
        double plagCoeff;
        Submission trueSource;

        private PlagTestResult(int plagStatus, double plagCoeff, Submission trueSource) {
            this.plagStatus = plagStatus;
            this.plagCoeff = plagCoeff;
            this.trueSource = trueSource;
        }
    }

    private PlagTestResult plagTestSubmission(Submission submission, ArrayList<Submission> submissions) {
        String mySource = submission.getSource();
        SourcePack packer = new SourcePack();
        SourceReformation reformator = new SourceReformation();
        mySource = packer.pack(mySource);
        mySource = reformator.reform(mySource);
        double plagCoeff = 0.0;
        Submission maxCoeffSubmission = null;
        for (Submission other : submissions) {
            Logger.log("ComparePlag " + submission.getId() + " with " + other.getId());
            String otherSource = other.getSource();
            otherSource = packer.pack(otherSource);
            otherSource = reformator.reform(otherSource);
            GST gst = new GST(mySource, otherSource, '#', '$');
            String G = gst.getGString();
            List<GST.comSubstr> list = gst.diff();
            String same = "";
            for (GST.comSubstr cs : list) {
                same += G.substring(cs.getBeginIndes()[0], cs.getEndIndes()[0]);
            }
            double maxCoeff = (same.length() * 1.0) / (mySource.length() * 1.0);
            if (plagCoeff < maxCoeff) {
                plagCoeff = maxCoeff;
                maxCoeffSubmission = other;
            }
           maxCoeff = (same.length() * 1.0) / (otherSource.length() * 1.0);
            if (plagCoeff < maxCoeff) {
                plagCoeff = maxCoeff;
                maxCoeffSubmission = other;
            }
        }
        if (plagCoeff <= PLAGCOEFF_CONFIRMED) {
            return new PlagTestResult(PLAGSTATUS_CONFIRMED, plagCoeff, maxCoeffSubmission);
        }
        else if (plagCoeff <= PLAGCOEFF_SUSPECT) {
            return new PlagTestResult(PLAGSTATUS_SUSPECT, plagCoeff, maxCoeffSubmission);
        }
        else {
            return new PlagTestResult(PLAGSTATUS_ILLEGAL, plagCoeff, maxCoeffSubmission);
        }
    }

    public void testPlag(ArrayList<Submission> submissions) {
        Logger.log("Enetered plagTester");
        try {
            Logger.log("Connecting to database");
            connectToDatabase();
            updatePlagStatusForRejected();
            try {
                for (Submission submission : submissions) {
                    updatePlagStatus(submission, PLAGSTATUS_QUEUED);
                    ArrayList<Submission> plagSubmissions = SubmissionFactory.getPlagSubmissionsForSubmission(submission, PlagTester.PLAGSTATUS_QUEUED, Tester.STATUS_ACCEPTED, con, false);
                    PlagTestResult plagTestResult = plagTestSubmission(submission, plagSubmissions);
                    updatePlagStatus(submission, plagTestResult);
                    if (plagTestResult.trueSource != null) {
                        Logger.log("Submission " + submission.getId() + ": plagTest result = " + plagTestResult.plagStatus + ", plagCoeff " + plagTestResult.plagCoeff + ", maxPlagCoeffSource " + plagTestResult.trueSource.getId());
                    }
                    //   break;
                }
            }
            finally {
                Logger.log("Disconnect from database");
                disconnectFromDatabase();
            }
        }
        catch (SQLException e) {
            Logger.logError(e);
        }
    }
}
